﻿using System;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace SDCloudSolution.SDCloud.ServiceAgent
{
    public class ServiceModel
    {
        public string Name { get; set; }
        public int PortNumber { get; set; }
        public string Url { get; set; }

        private string _processPath;
        private string _arguments;
        private int _timeOut;
        private ServiceState _state;
        private Process _process;
        private HttpClient _httpClient;

        private readonly string[] AcceptedHeaders = new[] { "application/json" };

        public ServiceState State
        {
            get { return _state; }
            private set {
                _state = value;
                if (_state == ServiceState.Down)
                {
                    RestartService();
                }
            }
        }

        public ServiceModel(string processPath, string arguments, string baseUrl, int portNumber, int timeOut)
        {
            _processPath = processPath;
            _arguments = arguments;
            _timeOut = timeOut;

            PortNumber = portNumber;
            Url = $"{baseUrl}:{portNumber}";

            StartProcess();

            SetupHttpClient();
        }
        
        ~ServiceModel()
        {
            _httpClient.Dispose();
            EndProcess();
        }

        private void SetupHttpClient()
        {
            _httpClient = new HttpClient
            {
                BaseAddress = new Uri(Url),
                Timeout = new TimeSpan(0, _timeOut, 0),
            };
            foreach (var header in AcceptedHeaders)
            {
                _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
            }
        }

        private void StartProcess()
        {
            State = ServiceState.Initialize;
            ProcessStartInfo startInfo = new ProcessStartInfo(_processPath)
            {
              Arguments = $"{_arguments} {PortNumber}",
              WindowStyle = ProcessWindowStyle.Normal,
            };
           
            _process = Process.Start(startInfo);
            var serviceState = ServiceState.Down;
            while (serviceState == ServiceState.Down)
            {
                serviceState = RefreshServiceState();
            }
            State = serviceState;
        }

        public void EndProcess()
        {
            if (!_process.HasExited)
            {
                _process.Kill();
            }
        }

        private void RestartService()
        {
            if (_process != null)
                EndProcess();

            StartProcess();           
        }

        public ServiceState RefreshServiceState()
        {
            try
            {
                var myRequest = (HttpWebRequest)WebRequest.Create(Url);
                var response = (HttpWebResponse)myRequest.GetResponse();
                return ServiceState.Open;
            }
            catch (Exception)
            {
                return ServiceState.Down;
            }
        }

        protected T CallService<T>(Func<HttpResponseMessage> callMethod
            , Func<HttpResponseMessage, T> responseHandler)
        {
            State = ServiceState.Busy;
            try
            {
                HttpResponseMessage response = callMethod();

                State = ServiceState.Open;
                if (response.IsSuccessStatusCode)
                {
                    return responseHandler(response);
                }

                throw new ServiceException($"Response status code: {response.StatusCode}. {response.ReasonPhrase}.")
                {
                     InternalServiceStatusCode = response.StatusCode,
                     InternalServiceMessage = response.Content.ReadAsStringAsync().Result,
                     Type = ServiceExceptionType.Expected
                };
            }
            catch (ServiceException)
            {
                // Prevent state failing.
                throw;
            }
            catch (Exception exception)
            {
                State = ServiceState.Down;
                throw new ServiceException($"Unexpected exception handled: {exception.Message}.", exception)
                {
                    Type = ServiceExceptionType.Unexpected
                };
            }
        }

        public Task<string> GetResponse(string getRoutePath)
        {
            return CallService<Task<string>>(() =>
                _httpClient.GetAsync(getRoutePath).Result,
                (HttpResponseMessage response) =>
                     response.Content.ReadAsStringAsync()
            );
        }

        public Task<string> PostResponse(string postRoutePath, HttpContent content)
        {
            return CallService<Task<string>>(() =>
                _httpClient.PostAsync(postRoutePath, content).Result,
                (HttpResponseMessage response) =>
                     response.Content.ReadAsStringAsync()
            );
        }
    }
}
